package services

import (
	"fmt"
	"github.com/gorhill/cronexpr"
	"sort"
	"time"
)

// 单封装每一个任务的结构体
type Entry struct {
	// 任务时间表达式
	Schedule cronexpr.Expression
	// 下一次执行的时间
	Next time.Time
	// 上一次执行的时间
	Prev time.Time
	// 具体执行什么
	Job
}

// true：删除
// false：不删除

type RemoveFunc func(e *Entry) bool
type Cron struct {
	// 任务列表
	entries []*Entry
	// 停止的管道
	stop chan struct{}
	// 添加的管道
	add chan *Entry
	// 删除的管道
	remove chan RemoveFunc
	// 复制的管道
	snapshot chan []*Entry
	// 标识任务似乎否正在运行
	running bool
}

// 提供一个构造函数，初始化
func New() *Cron {
	return &Cron{
		entries:  nil,
		stop:     make(chan struct{}),
		add:      make(chan *Entry),
		remove:   make(chan RemoveFunc),
		snapshot: make(chan []*Entry),
		running:  false,
	}
}

// 启动cron
func (c *Cron) Start() {
	// 标识任务正在运行
	c.running = true
	// 开启调度协程
	go c.run()
}

// 调度协程
// 1.遍历所有在运行的任务，根据当前时间，计算出下一次执行时间
// 2.循环根据下一次运行时间，对所有任务进行排序，得到最近一次执行时间
// 3.调度协程根据管道数据做不同调度
func (c *Cron) run() {
	// 1.遍历所有在运行的任务，根据当前时间，计算出下一次执行时间
	// 获取当前时间
	now := time.Now().Local()
	fmt.Println("run c.entries:::", c.entries)
	for _, entry := range c.entries {
		entry.Next = entry.Schedule.Next(now)
	}
	// 2. 循环根据下一次运行时间，对所有任务进行排序，得到最近一次执行时间
	for {
		fmt.Println("监听 c.entries")
		// 根据Entry的Next，对下一次运行时间，对任务进行排序
		sort.Sort(byTime(c.entries))
		// 获取最近一次要执行的时间
		var nearTime time.Time
		// 判断，若没有任务在运行，下一次执行时间赋值
		if len(c.entries) == 0 || c.entries[0].Next.IsZero() {
			nearTime = now.AddDate(10, 0, 0)
		} else {
			// 得到最近的执行时间
			nearTime = c.entries[0].Next
		}

		fmt.Println("nearTime::::", nearTime)

		// 3.调度协程根据管道数据做不同调度
		select {
		case now = <-time.After(nearTime.Sub(now)):
			// 执行任务，循环遍历，可能有多个任务同时执行
			fmt.Println(nearTime, "时间到")

			for _, e := range c.entries {
				if e.Next != nearTime {
					break
				}
				go e.Job.Run()
				// 更新下任务相关的时间字段
				e.Prev = e.Next
				e.Next = e.Schedule.Next(nearTime)
			}
		//  stop:     make(chan struct{}),
		//	add:      make(chan *Entry),
		//	remove:   make(chan RemoveFunc),
		//	snapshot: make(chan [] *Entry),
		case newEntry := <-c.add:

			c.entries = append(c.entries, newEntry)
			newEntry.Next = newEntry.Schedule.Next(now)
		case cb := <-c.remove:
			// 存储不删除的任务
			newEntries := make([]*Entry, 0)
			// 遍历之前的切片
			for _, e := range c.entries {
				if !cb(e) {
					newEntries = append(newEntries, e)
				}
			}
			// 删除处理后的切片，赋值给原切片
			c.entries = newEntries
		case <-c.snapshot:
			// 取到当前是所有任务复制，扔过滤给后续用
			c.snapshot <- c.snapshotEntry()
		case <-c.stop:
			return
		}
	}
}

// 复制任务
func (c *Cron) snapshotEntry() []*Entry {
	entries := []*Entry{}

	for _, e := range c.entries {
		// 因为是指针，若如下写法，最终会一样
		// entries = append(entries, e)
		entries = append(entries, &Entry{
			Schedule: e.Schedule,
			Next:     e.Next,
			Prev:     e.Prev,
			Job:      e.Job,
		})
	}
	return entries
}

// 切片类型
type byTime []*Entry

func (s byTime) Len() int {
	return len(s)
}

// 交换
func (s byTime) Swap(i, j int) {
	s[i], s[j] = s[j], s[i]
}

// 比较大小
func (s byTime) Less(i, j int) bool {
	// 根据Next字段排序
	// 判断下一次执行时间是Time类型的零值，表明任务时间无效
	if s[i].Next.IsZero() {
		return false
	}
	if s[j].Next.IsZero() {
		return true
	}
	// 比较i时间在j时间之前，若不在则返回false
	return s[i].Next.Before(s[j].Next)
}

// 添加任务到管道
// 参数：时间表达式，任务
func (c *Cron) AddJob(spec string, job Job) error {

	fmt.Println("c.running:::", c.running)
	// 解析时间表达式
	schedule, err := cronexpr.Parse(spec)
	if err != nil {
		return err
	}
	entry := &Entry{
		Schedule: *schedule,
		Job:      job,
	}
	if !c.running {
		// 未占用时，直接添加即可
		c.entries = append(c.entries, entry)
	}
	// 任务被占用时，add进管道等待处理
	c.add <- entry
	return nil
}

// 删除
func (c *Cron) RemoveJob(rf RemoveFunc) {
	c.remove <- rf
}

// 复制
func (c *Cron) Entries() []*Entry {
	fmt.Println("c.running::::", c.running)
	if !c.running {
		// 若占用，通过管道
		c.snapshot <- nil
		x := <-c.snapshot
		return x
	}
	fmt.Println("QQQQ:", c)
	return c.snapshotEntry()
}
